// This file contains some general utility functions that are not specific to CObject
#pragma once
#ifndef _KH_CORE_OBJECTUTIL_H
#define _KH_CORE_OBJECTUTIL_H
#include <Kharlia/Core/Common.h>
#include <boost/python/object/find_instance.hpp>
#include <hash_map>

namespace kharlia { namespace core { namespace util {
/// Callable python object used to construct instances of native objects
extern KH_CORE_DECLSPEC object GConstructObjectFunc;

/// Name resolver
extern KH_CORE_DECLSPEC object GResolveNameFunc;

/// Wrapper for object constructor

#define _CO_NUM_ARGS 8
#define _CO_ARGS(z, n, data) A##n a##n

#define _CO(z, n, data) \
    template <BOOST_PP_ENUM_PARAMS(n, class A)> \
    inline object ConstructObject(data cls, BOOST_PP_ENUM(n, _CO_ARGS, ~)) \
    { return GConstructObjectFunc(cls, BOOST_PP_ENUM_PARAMS(n, a)); }

#define _CO_EX(z, n, data) \
    template <class R BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_PARAMS(n, class A)> \
    inline shared_ptr<R> ConstructObjectEx(data cls BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM(n, _CO_ARGS, ~)) \
    { return extract<shared_ptr<R>>(GConstructObjectFunc(cls BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_PARAMS(n, a))); }

BOOST_PP_REPEAT_FROM_TO(1, _CO_NUM_ARGS, _CO, object)
BOOST_PP_REPEAT_FROM_TO(1, _CO_NUM_ARGS, _CO, str)
BOOST_PP_REPEAT(_CO_NUM_ARGS, _CO_EX, object)
BOOST_PP_REPEAT(_CO_NUM_ARGS, _CO_EX, str)

#undef _CO
#undef _CO_EX
#undef _CO_ARGS
#undef _CO_NUM_ARGS

/// Name resolver wrapper
inline object ResolveName(str name) {
    return GResolveNameFunc(name);
}
inline object ResolveName(object name) {
    return GResolveNameFunc(name);
}

/// Resolve name and extract type T
template <class ExtractType>
inline ExtractType ResolveNameEx(str name) {
    return extract<ExtractType>(GResolveNameFunc(name));
}

/// Returns a python weakref to obj. If obj is a weak ref it is returned as-is.
KH_CORE_DECLSPEC object GetWeakRef(object obj, object callback=object());

/// Returns the Boost.Python.class object for the specified type info or None if
/// one does not exist.
inline object GetTypeObject(py::type_info info) {
    const converter::registration* reg =
        converter::registry::query(info);
    if (reg) {
        return object(handle<>(borrowed(reg->get_class_object())));
    }
    return object();
}

/// Returns true if a shared_ptr is managing a python object
template <class T>
inline bool IsManagingPyObject(const shared_ptr<T>& p) {
    return boost::get_deleter<converter::shared_ptr_deleter>(p) != NULL;
}

/// Calls nextFunc and sets out to the result. Returns true if this was
/// successful, false if StopIteration was caught.
KH_CORE_DECLSPEC bool Iterate(const object& nextFunc, object& out);

// Returns object.__iter__()
inline object GetIter(const object& object) {
    return object.attr("__iter__")();
}

// Returns object.__iter__().next
// Use this function for retrieving iterators for use with Iterate().
inline object GetIterNextFunc(const object& object) {
    return object.attr("__iter__")().attr("next");
}

struct _PyTypeInfoHash: public stdext::hash_compare<py::type_info> {
    size_t operator()(const py::type_info& ti) const {
        return stdext::hash_value(ti.name());
    }
    bool operator()(const py::type_info& ti, const py::type_info& ti2) const {
        return ti < ti2;
    }
};

typedef stdext::hash_map<py::type_info, py::type_info, _PyTypeInfoHash> FValueToPtrRegistry;

// Registry of value type_info to its held pointer type_info in Boost.Python
// This is used to get the type_info of shared_ptr<T> when we only have
// the type_info object of T available. This allows us to extract the held
// shared_ptr<D> from a Boost.Python pointer holder using its type_info when
// we only have a pointer to T.
KH_CORE_DECLSPEC extern FValueToPtrRegistry GValueToSharedPtrRegistry;

// Registers type info of ValueType and corresponding shared_ptr<ValueType>
template <class ValueType>
inline void RegisterValueToSharedPtr() {
    GValueToSharedPtrRegistry[type_id<ValueType>()] = type_id<shared_ptr<ValueType>>();
}

// Extracts the held shared_ptr<T> of source, rather than creating a new shared_ptr<T>
// that is managing the python object. This function requires that
// RegisterValueToSharedPtr<DerivedType>() has been called.
//
// TODO: Consider Boost.Python source edit or special pointer holder
template <class T>
shared_ptr<T> ExtractHeldSharedPtr(object source) {
    // Dynamic type checking will succed
    T* vptr = (T*)objects::find_instance_impl(source.ptr(), type_id<T>(), false);
    // Discover most derived type
    py::type_info src_t = typeid(*vptr);
    // Lookup the corresponding shared_ptr in registry
    FValueToPtrRegistry::iterator it = GValueToSharedPtrRegistry.find(src_t);
    if (it == GValueToSharedPtrRegistry.end())
        throw RuntimeError("unregistered conversion");
    // Retrieve the held derived shared_ptr from the instance
    py::type_info dst_t = it->second;
    shared_ptr<T>* ptr = (shared_ptr<T>*)objects::find_instance_impl(source.ptr(), dst_t, false);
    if (!ptr)
        throw RuntimeError("held shared_ptr instance not found");
    return *ptr;
}

} // namespace util

using namespace util;

}} //namespace Kharlia::core

namespace boost { namespace python { namespace converter {

// Implementation for specialization of shared_ptr_from_python that returns
// the actual held shared_ptr rather than a special shared_ptr that manages
// the python object.
//
// ExtractHeldSharedPtr() is used for individual cases, while this is used
// to specify conversion for all cases.
template <class T>
struct held_shared_ptr_from_python_impl {
protected:
    static void* convertible(PyObject* p) {
        if (p == Py_None)
            return p;
        
        return get_lvalue_from_python(p, registered<T>::converters);
    }

    static void construct(PyObject* source, rvalue_from_python_stage1_data* data) {
        using namespace kharlia::core;
        void* const storage = ((rvalue_from_python_storage<shared_ptr<T> >*)data)->storage.bytes;
        if (data->convertible == source)
            new (storage) shared_ptr<T>();
        else {
            // inline ExtractHeldSharedPtr<T>()
            T* vptr = static_cast<T*>(data->convertible);
            type_info src_t = typeid(*vptr);
            FValueToPtrRegistry::iterator it = GValueToSharedPtrRegistry.find(src_t);
            if (it == GValueToSharedPtrRegistry.end())
                throw RuntimeError("unregistered conversion");
            type_info dst_t = it->second;
            shared_ptr<T>* ptr = (shared_ptr<T>*)objects::find_instance_impl(source, dst_t, false);
            if (!ptr)
                throw RuntimeError("held shared_ptr instance not found");
            new(storage) shared_ptr<T>(*ptr);
        }
        
        data->convertible = storage;
    }
};

// Macro used to enable shared_ptr<TYPE> from-python conversions to return
// the held shared_ptr. Must be used in global namespace before class_<TYPE> is
// constructed.
#define KH_ENABLE_HELD_SHARED_PTR_FROM_PYTHON(TYPE) \
    namespace boost { namespace python { namespace converter {\
    template <>\
    struct shared_ptr_from_python<TYPE>:\
        public held_shared_ptr_from_python_impl<TYPE>\
    {\
        typedef TYPE T;\
        typedef held_shared_ptr_from_python_impl<TYPE> impl_type;\
        shared_ptr_from_python() {\
            kharlia::core::RegisterValueToSharedPtr<T>();\
            registry::insert(\
                &impl_type::convertible,\
                &impl_type::construct,\
                type_id<shared_ptr<T>>(),\
                &expected_from_python_type_direct<T>::get_pytype\
            );\
        }\
    };}}}

}}}

#endif